%{
#include "expr.h"
#include "stmt.h"
#include "symbol.h"
#include <cstdio>
#include <cassert>
#include <vector>
#include <string>

extern "C" int yylex(void);
extern int yylineno;
extern char *yytext;
extern Tree *program_node;
void yyerror(char const *);

#define YYTRACE(x...) printf(x);putchar(' ');putchar('\n')
//#define YYTRACE(x...)  
#define YYERROR_VERBOSE 1
#define YYDEBUG 1

extern SymbolTable symbolTable;

%}

%locations

%union{
    int     ival;
    char*   str;
    Tree*   tree;
}

%token INT
%token STRTYPE
%token FUNCTYPE

%token WHILE
%token IF
%token ELSE
%token END

%token <str> ID
%token <str> NUMBER
%token <str> STRING

%token EQ
%token LE
%token GE
%token NE

%type <tree> program
%type <tree> expr variable func_name
%type <tree> block xblock stmt variable_decl assignment
%type <tree> func_def func_call para_list variable_list expr_list
%type <tree> while_stmt if_stmt
%type <ival> type

%right '='
%left '+' '-'
%left '*' '/'

%start program

%%
program: block { YYTRACE("program-->block");
                 Block* block = dynamic_cast<Block*>($1);
                 assert(block);
                 program_node = new Program(block);
               };

type:INT        { $$ = $<ival>1; }
    |STRTYPE    { $$ = $<ival>1; }
    ;

variable: ID    { $$ = new VariableExpr($1); free($1) };

variable_decl: type variable { 
                  YYTRACE("variable_decl-->type variable");
                  VariableExpr *vexpr = dynamic_cast<VariableExpr*>($2);

                  assert(vexpr && "variable_decl error");
                  vexpr->defLine = yylineno;

                  $$ = new VariableDeclareStatement($1,vexpr);
               }; //TODO: variable_list support
             
assignment: variable '=' expr   { YYTRACE("assignment-->variable = expr");$$ = new AssignmentStatement($1,$3); };


func_def: FUNCTYPE func_name para_list ':' block END {
                YYTRACE("func_def-->FUNCTYPE func_name para_list : block END");
                FunctionNameExpr    *expr = dynamic_cast<FunctionNameExpr*>($2);
                ParameterListStatement *stmt = dynamic_cast<ParameterListStatement*>($3);
                Block  *blk = dynamic_cast<Block*>($5);
                
                assert(expr && blk && "invalid function definition");
                expr->defLine = yylineno;
                FunctionDefStatement *funcdef = new FunctionDefStatement(expr,stmt,blk);
                
                $$ = funcdef;
          };

func_call: func_name '(' expr_list ')' {
                YYTRACE("func_call-->func_name ( expr_list )");
                FunctionNameExpr    *expr = dynamic_cast<FunctionNameExpr*>($1);
                ExprListStatement   *stmt = dynamic_cast<ExprListStatement*>($3);
                
                assert(expr && stmt && "invalid function call");
                FunctionCallStatement *funccall = new FunctionCallStatement(expr,stmt);
                $$ = funccall;
          };

func_name: ID   { YYTRACE("func_name-->ID");$$ = new FunctionNameExpr($1); };

para_list: type variable    {
                YYTRACE("para_list-->type variable");
                ParameterListStatement  *stmt = new ParameterListStatement();
                
                VariableExpr    *vexpr = dynamic_cast<VariableExpr*>($2);
                assert(vexpr);
                
                stmt->variables.push_back(vexpr);
                $$ = stmt;
           }
         | para_list ',' type variable  {
                YYTRACE("para_list-->para_list , type variable");
                VariableExpr    *vexpr = dynamic_cast<VariableExpr*>($4);
                assert(vexpr);
                vexpr->defLine = yylineno;
                
                ParameterListStatement  *stmt = dynamic_cast<ParameterListStatement*>($1);
                assert(stmt);
                
                stmt->variables.push_back(vexpr);
                $$ = stmt;
           }
         | { YYTRACE("para_list-->null");
             $$ = 0;
           }
         ;

variable_list: variable {
                YYTRACE("variable_list-->variable");
                VariableListStatement *stmt = new VariableListStatement();
                
                VariableExpr    *vexpr = dynamic_cast<VariableExpr*>($1);
                assert(vexpr && "should be VariableExpr");
                
                stmt->variables.push_back(vexpr);
                $$ = stmt;
             }
            |variable_list ',' variable {
                YYTRACE("variable_list-->variable_list , variable");
                
                VariableExpr    *vexpr = dynamic_cast<VariableExpr*>($3);
                assert(vexpr && "should be VariableExpr");
                
                VariableListStatement  *stmt = dynamic_cast<VariableListStatement*>($1);
                assert(stmt && "should be VariableListStatement");
                
                stmt->variables.push_back(vexpr);
                $$ = stmt;
              }
            ;

expr_list: expr {
                YYTRACE("expr_list-->expr");
                ExprListStatement *stmt = new ExprListStatement();
                
                Expr    *expr = dynamic_cast<Expr*>($1);
                assert(expr && "should be Expr");
                
                stmt->variables.push_back(expr);
                $$ = stmt;
             }
            |expr_list ',' expr {
                YYTRACE("variable_list-->variable_list , variable");
                
                Expr    *expr = dynamic_cast<Expr*>($3);
                assert(expr && "should be Expr");
                
                ExprListStatement  *stmt = dynamic_cast<ExprListStatement*>($1);
                assert(stmt && "should be ExprListStatement");
                
                stmt->variables.push_back(expr);
                $$ = stmt;
              }
            ;

while_stmt: WHILE expr ':' xblock END    {
                YYTRACE("while_stmt-->while expr: xblock end");
                Expr *expr = dynamic_cast<Expr*>($2);
                Block *block = 0;
                
                // if the block is not empty
                if($4){
                    block = dynamic_cast<Block*>($4);
                }

                WhileStatement *stmt = new WhileStatement(expr,block);
                $$ = stmt;
            }
          ;

if_stmt: IF expr ':' xblock END {
                YYTRACE("if_stmt-->if expr: xblock end");
                Expr *expr = dynamic_cast<Expr*>($2);
                Block *block = 0;
                
                if($4){
                    block = dynamic_cast<Block*>($4);
                }
                
                IfStatement *stmt = new IfStatement(expr,block,0);
                $$ = stmt;
        }
       | IF expr ':' xblock ELSE ':' xblock END {
                YYTRACE("if_stmt-->if expr: xblock else: xblock end");
                Expr *expr = dynamic_cast<Expr*>($2);
                Block *block1 = 0;
                Block *block2 = 0;
                
                if($4){
                    block1 = dynamic_cast<Block*>($4);
                }
                if($7){
                    block2 = dynamic_cast<Block*>($7);
                }
                
                IfStatement *stmt = new IfStatement(expr,block1,block2);
                $$ = stmt;
        }
       ;

expr: NUMBER  {
                YYTRACE("expr-->NUMBER");
                $$ = new ConstantExpr($1,ConstantExpr::CONST_INTEGER);
                free($1);
              }
    | STRING  {
                YYTRACE("expr-->STRING");
                $$ = new ConstantExpr($1,ConstantExpr::CONST_STRING);
                free($1);
              }
    | variable { YYTRACE("expr-->variable"); $$ = $1; }
    | expr '+' expr { YYTRACE("expr-->expr+expr");$$ = new AddExpr($1,$3); }
    | expr '-' expr { YYTRACE("expr-->expr-expr");$$ = new MinusExpr($1,$3); }
    | expr '*' expr { YYTRACE("expr-->expr*expr");$$ = new MulExpr($1,$3); }
    | expr '/' expr { YYTRACE("expr-->expr/expr");$$ = new DivExpr($1,$3); }
    ;

//nonull_stmt: expr stmt { $$ = new Statement($1,$2); };

stmt: variable_decl { $$ = $1; }
    | assignment { $$ = $1; }
    | func_def { $$ = $1; }
    | func_call { $$ = $1; }
    | while_stmt { $$ = $1; }
    | if_stmt { $$ = $1; }
    ;

block: stmt { YYTRACE("block-->stmt");
              Block *blk = new Block();
              blk->statements.push_back($1);
              $$ = blk;
            }
     | block stmt { YYTRACE("block-->block stmt");
                    Block *blk = dynamic_cast<Block*>($1);
                    if(!blk){
                        yyerror("block error");
                    }
                    
                    blk->statements.push_back($2);
                    $$ = $1;
                  }
     ;

xblock: block { YYTRACE("xblock-->block");
                $$ = $1;
              }
       | { YYTRACE("xblock-->null");
           $$ = 0;
         }
       ;
%%

void yyerror(char const * msg){
    printf("#line:%d  %s at %s\n",yylineno,msg,yytext);
}

